/******************************************************************************
 *      Mestrado em Ciência da Computação - UFMS
 *		Redes de Computadores - Prof. Dr. Ronaldo Alves Ferreira
 *		Alunos: Geraldo Landre, Marcel Tolentino, Vinícius Bueno
 *****************************************************************************/

#include "server.h"

/*Variavel Condicional e Mutex */
pthread_mutex_t count_mutex   = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  condition_var = PTHREAD_COND_INITIALIZER;


/*
	#########################################################################
	############## Implementação das funções
	#########################################################################
*/

/* implementação do srv_getboundsockfd */
int srv_getboundsockfd(struct addrinfo *srv,vstring *port,int lg_reuseaddr)
{
    struct addrinfo hints;
    int sockfd,status;

    /* preencher o hints */
    memset(&hints,0,sizeof(struct addrinfo));

    /* preencher com as informações sobre o servidor local */
    hints.ai_family = AF_UNSPEC;		/* não importa, poderia ser AF_INET (IP), ou AF_INET6 (IPv6), ... */
                                            /* usar AF_UNSPEC aumenta a compatibilidade com IPv6 (e outros) */
    hints.ai_socktype = SOCK_STREAM;	/* TCP stream sockets */
    hints.ai_flags = AI_PASSIVE;		/* diz para que o SO atribua o meu endereço local */

    /* getaddrinfo */
    status = getaddrinfo(NULL,port->pt,&hints,&srv);
    if( status != 0 )
        fprintf(stderr, "erro srv_getaddrinfo: %s\n", gai_strerror(status));

    printf ("srv_getaddrinfo status: %d\n", status);

    /* sock_open */
    printf("chamada do sock_open no gethttosock\n");
    sockfd = sock_open(srv);

    /* Caso a porta já esteja em uso, solicita o reuso da mesma, */
    /* para evitar a mensagem de erro: "Address already in use" */
    if(lg_reuseaddr)
    {
        fprintf(stdout, "[srv_bind]: reuseaddr=true\n");
        sock_reuseaddr(sockfd);
    }
    else
        fprintf(stdout, "[srv_bind]: reuseaddr=false\n");

    /* efetua o bind */
    status = bind(sockfd, srv->ai_addr, srv->ai_addrlen);

    if( status == -1 )
    {
        perror("erro bind");
        exit(EXIT_FAILURE);
    }

    return sockfd;
}

int srv_listen(int sockfd,int backlog)
{
    /* iniciar o listen */
    int status = listen(sockfd, backlog);
    if(status == -1)
    {
        fprintf(stderr, "erro ao efetuar o listen!\n");
        exit(EXIT_FAILURE);
    }
    fprintf(stdout, "[Listen efetivado!]\n");

    return status;
}

/**
 * Cria uma conexão com um servidor
 * @param url Endereço do Servidor
 * @param service Porta ou Serviço a se conectar no servidor, ex.: "80", "http"
 * @param output_error Variavel de saída, para ser armazenada uma mensagem em
 *          caso de erro.
 * @return Socket Descriptor da conexão
 */
int srv_connect(vstring *url,vstring *service,vstring *output_error,struct addrinfo **servinfo)
{
    struct addrinfo hints,*res = NULL;
    int sockfd = 0,status = 0;

    alloc_string(&output_error,256);
    // preencher o hints
    memset(&hints, 0, sizeof(struct addrinfo));
    // preencher com as informações sobre o servidor local
    hints.ai_family = AF_UNSPEC;	// não importa, poderia ser AF_INET (IP), ou AF_INET6 (IPv6), ... 
                                        // usar AF_UNSPEC aumenta a compatibilidade com IPv6 (e outros)
    hints.ai_socktype = SOCK_STREAM;	// TCP stream sockets

    // verifica a porta
    if( service == NULL || strcmp(service->pt,"") != 0)
        copy_string(&service,"80");

    //printf("[srv_connect] getting addrinfo...\n");
    status = getaddrinfo(url->pt,service->pt,&hints,&res);
    // getaddrinfo
    if(status != 0)
        return -1; // erro
    *servinfo = res;
    // Criar o socket
    if((sockfd = sock_open(res)) == -1)
        return -1; // erro
    // Conectar com o servidor (CONNECT)
    if((status = connect(sockfd,res->ai_addr,res->ai_addrlen)) == -1)
        return -1; // erro
    return sockfd;
}

int recvtimeout(int s,vstring **buf,int timeout)
{
    fd_set fds;
    int n;
    struct timeval tv;

    // set up the file descriptor set
    FD_ZERO(&fds);
    FD_SET(s, &fds);

    // set up the struct timeval for the timeout
    tv.tv_sec = timeout;
    tv.tv_usec = 0;

    // wait until timeout or data received
    n = select(s+1, &fds, NULL, NULL, &tv);
    if(n == 0) return -2; // timeout!
    if(n == -1) return -1; // error
    // data must be here, so do a normal recv()
    (*buf)->length = recv(s,(*buf)->pt,(*buf)->mem_size,0);

    return (*buf)->length;
}

void server_recv(int sockfd,header_data **hd)
{
    int parsed = 0;
    vstring *buf = NULL,*aux = NULL;
    
    do
    {
        alloc_string(&buf,BUFFER);
        (*hd)->last_irc = recvtimeout(sockfd,&buf,TIMEOUT);
        
        if((*hd)->last_irc > 0)
        {
            concat_vstring(&aux,buf);
            concat_vstring(&((*hd)->msg),buf);

            if(get_index_occour(&aux,END_HEADER) > 0)
                parsed = parse(aux,hd);                       
        }    
        else
            if((*hd)->last_irc == -1)
                (*hd)->error = ERR_BAD_REQUEST;
        free_string(&buf);
    
    }while((*hd)->last_irc > 0 && !parsed && !(*hd)->error);
    printf("depois de fazer o recv todo que tinha pra pegar o cabeçalho\n\n");
    if(!is_null(&aux))
        copy_vstring(&((*hd)->msg),aux);   
    free_string(&aux);
}

void server_pass(int serv_sock,header_data **hd)
{
    int aux_irc = -2;
    vstring *buf = NULL,*aux = NULL,*hexa = NULL;//,*prefix_aux = NULL;//,*path = NULL;    
    
    server_recv(serv_sock,hd);       
    aux = get_header(hd);
    alloc_string(&buf,BUFFER);
       
    if((*hd)->content_length)
    {        
        printf("MENSAGEM PEQUENA\n\n\n");
        while((*hd)->msg->length < (*hd)->content_length)
        {            
            aux_irc = (*hd)->last_irc;
            (*hd)->last_irc = recvtimeout(serv_sock,&buf,TIMEOUT);

            if((*hd)->last_irc > 0)
            {
                concat_vstring(&((*hd)->msg),buf);                                               
            }            
        }  
        concat_vstring(&aux,(*hd)->msg);
        send((*hd)->client_socket,aux->pt,aux->length,0);                        
    }
    else
    {
        if((*hd)->chunked)
        {
            printf("ENTROU NO IF DO CHUNK\n\n\n");
            if(!is_null(&((*hd)->msg)))
            {                
                concat_vstring(&aux,(*hd)->msg);
                send((*hd)->client_socket,aux->pt,aux->length,0);
            }
            
            do
            {
                aux_irc = (*hd)->last_irc;
                (*hd)->last_irc = recvtimeout(serv_sock,&buf,TIMEOUT);
                if((*hd)->last_irc > 0)
                {
                    concat_vstring(&((*hd)->msg),buf);
                    send((*hd)->client_socket,buf->pt,buf->length,0);
                }
            }while((*hd)->last_irc > 0);
            
            if(aux_irc > 0)
            {
                concat_string(&((*hd)->msg),"0");
                send((*hd)->client_socket,"0",1,0);
            }
        }
        else
        {
            printf("ENTROU NO ELSE DO CHUNK\n\n\n");
            if(!is_null(&((*hd)->msg)))
            {
                get_hexadecimal(&hexa,(*hd)->msg->length);
                concat_string(&hexa,END_HEADER_LINE);
                concat_vstring(&hexa,(*hd)->msg); // até aqui criou o hexa e juntou o que tinha do corpo
                concat_vstring(&aux,hexa); // pegou o cabeçalho criado e juntou o corpo a ele
                send((*hd)->client_socket,aux->pt,aux->length,0);     
                copy_vstring(&((*hd)->msg),hexa);
                free_string(&aux);
                free_string(&hexa);
            }            
                
            do
            { 
                aux_irc = (*hd)->last_irc;
                (*hd)->last_irc = recvtimeout(serv_sock,&buf,TIMEOUT);
                if((*hd)->last_irc > 0)
                {
                    get_hexadecimal(&hexa,buf->length);
                    concat_string(&hexa,END_HEADER_LINE);
                    concat_vstring(&hexa,buf);
                    send((*hd)->client_socket,hexa->pt,hexa->length,0);
                    concat_vstring(&((*hd)->msg),hexa);
                }                 
                free_string(&hexa);                
            }while((*hd)->last_irc > 0);          
        }   
    }      
    (*hd)->last_irc = 1;
    
    free_string(&aux);
    free_string(&buf);
    free_string(&hexa);
    printf("finalizou o envio no server_pass\n");
    //copy_string(&path,"/home/vinicius/Mestrado/proxy/bin/www/globo do proxy");
    //write_file(path,(*hd)->msg);    
}

/*
	 Deverá existir uma instância separada desta função para cada conexão. Ela trata toda a comunicação
	 uma vez que uma conexão tenha sido estabelecida.Utilizado tanto no servidor multi-processo quanto
	 no servidor mult-thread
 */
void connection_handle(int sock)
{
    vstring* resp = NULL;
    int iSc = 0;
    header_data *header = new_header();

    printf("Iniciar Conexão Persistente ...\n");
    do
    {
        reset_header(&header);
        header->client_socket = sock;
        printf("antes do server recv\n");
        server_recv(sock,&header);
        if(header->last_irc == -2 && is_empty(header))
        {
	    printf("Timeout!! Conexão Fechada!\n");
	    break;
	}

        if(header->error)
            resp = get_msg_error(&header, NULL);
        else
            if(header->last_irc != -1 && !is_empty(header))
            {
                printf("vai pro handle request:\n\n");
                print_header_data(header);
                if(!handle_request(&header))
                    resp = get_msg_error(&header, NULL);
            }
        
        if(!is_null(&resp))
        {
            printf("-------------------------------------------------------\n");
            printf("Nossa resposta tem tamanho %i:\n",resp->length);
            print_string(header->msg);
            printf("-------------------------------------------------------\n");
            iSc = send(sock,resp->pt,resp->length,0);

            if(iSc == -1)
                perror("erro no send");        
            free_string(&resp);
        }        
        printf("fim do do/while\n");
        //print_header_data(header);
    }while(header->alive && header->last_irc > 0);

    shutdown(sock,SHUT_RDWR);
    free_string(&resp);
}